﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Xml.Linq;
using Microsoft.Win32;
using Cqrs.StateMachineDesigner.Database;
using Cqrs.Infrastructure;
using System.Reflection;
using Cqrs.StateMachineDesigner.Resources;
using Cqrs.StateMachineDesigner.CustomCode;

namespace Cqrs.StateMachineDesigner
{
    public partial class DesignerCanvas
    {
        // todo - bit of a hack here - static vars?.
        public static List<DomainProperty> AllDomainProperties = new List<DomainProperty>();
        public static string DbLocation { get; set; }
        public static String SmfFile { get; set; }

        private ComponentModel _componentModel = new ComponentModel();

        public static RoutedCommand Debug = new RoutedCommand();
        public static RoutedCommand LoadAggregate = new RoutedCommand();
        public static RoutedCommand ExportToXml = new RoutedCommand();
        public static RoutedCommand ImportScxml = new RoutedCommand();
        public static RoutedCommand SaveToDb = new RoutedCommand();
        public static RoutedCommand LoadFromDb = new RoutedCommand();
        public static RoutedCommand GenerateCode = new RoutedCommand();
        public static RoutedCommand AddEditProperties = new RoutedCommand();
        public static RoutedCommand LoadComponent = new RoutedCommand();
        public static RoutedCommand SaveAs = new RoutedCommand();

        public string AssemblyType { get; set; }
        public string AssemblyLocation { get; set; }

        public bool IsAssemblyLoaded
        {
            get { return (bool)GetValue(IsAssemblyLoadedProperty); }
            set { SetValue(IsAssemblyLoadedProperty, value); }
        }
        public static readonly DependencyProperty IsAssemblyLoadedProperty = DependencyProperty.Register("IsAssemblyLoaded", typeof(bool), typeof(DesignerCanvas));

        public string Details
        {
            get { return (string)GetValue(DetailsProperty); }
            set { SetValue(DetailsProperty, value); }
        }
        public static readonly DependencyProperty DetailsProperty = DependencyProperty.Register("Details", typeof(string), typeof(DesignerCanvas));

        public string DomainName
        {
            get { return (string)GetValue(DomainNameProperty); }
            set { SetValue(DomainNameProperty, value); }
        }
        public static readonly DependencyProperty DomainNameProperty =
            DependencyProperty.Register("DomainName", typeof(string), typeof(DesignerCanvas));

        public string Namespace
        {
            get { return (string)GetValue(NamespaceProperty); }
            set { SetValue(NamespaceProperty, value); }
        }
        public static readonly DependencyProperty NamespaceProperty = DependencyProperty.Register("Namespace", typeof(string), typeof(DesignerCanvas));

        public StateDictionary DesignerStateDictionary { get; set; }

        private void ExportToXml_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                SaveToScxmlFile();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void ImportScxml_Executed(object sender, ExecutedRoutedEventArgs e)
        {

            try
            {

                OpenFileDialog openFile = new OpenFileDialog();
                openFile.Filter = "Scxml Files (*.xml)|*.xml|All Files (*.*)|*.*";

                if (openFile.ShowDialog() == true)
                {
                    string fileName = openFile.FileName;
                    this.DomainName = openFile.SafeFileName.Replace(".xml", "");

                    XElement scXml = XElement.Load(fileName);
                    Dictionary<string, Guid> states = new Dictionary<string, Guid>();

                    double top = 10;
                    double left = 10;

                    string initialState = "";
                    if (scXml.Attribute("initial") != null)
                    {
                        initialState = scXml.Attribute("initial").Value;
                    }

                    foreach (var state in scXml.Elements())
                    {
                        // todo - refactor this code
                        StateType stateType = StateType.State;
                        string stateName = "";
                        if (state.Attribute("id") != null)
                        {
                            stateName = state.Attribute("id").Value;
                        }
                        else
                            break;

                        XElement gridXElement;
                        if (initialState == stateName)
                        {
                            gridXElement = XElement.Parse(IconResources.StartGridXml);
                            stateType = StateType.Start;
                        }
                        else
                        {
                            if (state.Name.LocalName == "Final")
                            {
                                gridXElement = XElement.Parse(IconResources.EndGridXml);
                                stateType = StateType.End;
                            }
                            else
                                gridXElement = XElement.Parse(IconResources.StateGridXml);
                        }

                        DesignerItem newState = AddStateItemToCanvas(Guid.NewGuid(), stateType,
                                                state.Attribute("id").Value, top, left, gridXElement);
                        states.Add(newState.DisplayText, newState.ID);

                        top += 100;
                        left += 100;
                    }

                    foreach (var state in scXml.Elements())
                    {
                        // todo - refactor this code
                        string stateName = "";
                        if (state.Attribute("id") != null)
                        {
                            stateName = state.Attribute("id").Value;
                        }
                        else
                            break;

                        foreach (var transition in state.Elements())
                        {
                            // todo - refactor this code
                            string transitionName = "";
                            if (transition.Attribute("event") != null)
                            {
                                transitionName = transition.Attribute("event").Value;
                            }
                            else
                                break;
                            // todo - refactor this code
                            string targetName = "";
                            if (transition.Attribute("target") != null)
                            {
                                targetName = transition.Attribute("target").Value;
                            }
                            else
                                break;

                            ConnectDesignerItems(transitionName, 1, states[stateName], states[targetName],
                                ConnectorOrientation.Right.ToString(), ConnectorOrientation.Left.ToString(), "", null);
                        }
                    }

                }

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

        }

        private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(DesignerCanvas.SmfFile))
                    SaveAs_Executed(sender, e);
                else
                    SaveSmfFile(DesignerCanvas.SmfFile);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void LoadAggregate_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            AggregateList aggregates = new AggregateList();
            Assembly assembly = Assembly.LoadFrom(AssemblyLocation);
            try
            {

                Guid aggregateId = aggregates.DisplayAggregates(
                    assembly.GetTypes().Where(p => p.AssemblyQualifiedName == AssemblyType).First(),
                    AssemblyLocation,
                    DesignerCanvas.SmfFile,
                    DesignerCanvas.DbLocation
                 );

                List<Event> events = EventStoreInstance
                    .Instance.GetEventsForAggregate(aggregateId, assembly);
            }
            catch (Exception ex)
            {
                aggregates.Close();
                MessageBox.Show(ex.Message);
            }

        }

        private void LoadComponent_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                OpenComponent();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        private void AddEditProperties_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            List<Tuple<string, string>> events = new List<Tuple<string, string>>();
            DomainProperties properties = new DomainProperties();
            try
            {                
                DomainProperties.DomainPropertyModel domainPropertyModel = new DomainProperties.DomainPropertyModel();
                domainPropertyModel.DomainProperties = AllDomainProperties;
                domainPropertyModel.Name = this.DomainName;
                domainPropertyModel.Namespace = this.Namespace;
                domainPropertyModel = properties.ShowForm(domainPropertyModel);
                AllDomainProperties = domainPropertyModel.DomainProperties;
                this.DomainName = domainPropertyModel.Name;
                this.Namespace = domainPropertyModel.Namespace;
            }
            catch (Exception ex)
            {
                properties.Close();
                MessageBox.Show(ex.ToString());
            }
        }

        private void GenerateCode_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            List<Generator.CommandEventModel> commandEventModel = new List<Generator.CommandEventModel>();

            try
            {
                StateDictionary stateDictionary = BuildStateDictionary();
                foreach (var state in stateDictionary)
                {
                    foreach (var transitionKeyValue in state.Value)
                    {
                        Generator.CommandEventModel transition =
                            new Generator.CommandEventModel(
                                transitionKeyValue.Value.Event,
                                transitionKeyValue.Value.AssociatedCommand,
                                transitionKeyValue.Value.AffectedProperties,
                                stateDictionary.InitialState == state.Key.Name
                            );

                        if (commandEventModel.Exists(p => p.EventName == transition.EventName))
                        {
                            Generator.CommandEventModel eventModel =
                                commandEventModel.Find(p => p.EventName == transition.EventName);
                            foreach (DomainProperty affectedProperty in transition.AffectedProperties)
                            {
                                if (eventModel.AffectedProperties.Count(p => p.Name == affectedProperty.Name) == 0)
                                    eventModel.AffectedProperties.Add(affectedProperty);
                            }

                        }
                        else
                            commandEventModel.Add(transition);
                    }
                }

                Generator.CodeGenerator codeGen = new Generator.CodeGenerator();
                codeGen.ShowForm(Namespace, DomainName, commandEventModel, DesignerCanvas.AllDomainProperties);

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        private DesignerItem AddStateItemToCanvas(Guid id, StateType stateType, string displayText, double top, double left, XElement content)
        {
            XElement stateItem = XElement.Parse(IconResources.DesignerItemXml);
            stateItem.SetElementValue("Top", top);
            stateItem.SetElementValue("Left", left);
            stateItem.SetElementValue("ID", id);

            stateItem.Add(content);
            DesignerItem item = AddDesignerItemToCanvas(stateItem, stateType);
            item.DisplayText = displayText;
            return item;

        }

        private StateDictionary BuildStateDictionary()
        {
            IEnumerable<DesignerItem> designerItems = this.Children.OfType<DesignerItem>();
            IEnumerable<Connection> connections = this.Children.OfType<Connection>();
            StateDictionary states = new StateDictionary();

            foreach (var designerItem in designerItems)
            {
                Dictionary<string, Transition> transitions = new Dictionary<string, Transition>();
                if (designerItem.StateType == StateType.Start)
                {
                    states.InitialState = designerItem.DisplayText;
                }

                if (designerItem.StateType == StateType.End)
                {
                    states.FinalState = designerItem.DisplayText;
                }

                states.Add(new State(designerItem.ID, designerItem.DisplayText), transitions);
            }

            foreach (var connection in connections)
            {
                Transition transition = new Transition();
                transition.Event = connection.Event;
                transition.AssociatedCommand = connection.Command;
                transition.NextState = connection.Sink.ParentDesignerItem.DisplayText;
                transition.AffectedProperties = connection.DomainProperties;

                DesignerItem sourceState = connection.Source.ParentDesignerItem;
                try
                {
                    states[new State(sourceState.ID, sourceState.DisplayText)].Add(connection.Event, transition);
                }
                catch (ArgumentException)
                {

                    throw new ApplicationException(
                        string.Format("The '{0}' State is a source multiple Events of type '{1}'.  Please rename one of the existing events.",
                        sourceState.DisplayText,
                        connection.Event));
                }
                catch (Exception ex) { throw ex; }
            }
            return states;
        }

        private void SaveToScxmlFile()
        {
            string fileName;
            SaveFileDialog saveFile = new SaveFileDialog();
            saveFile.Filter = "Scxml Files | *.xml";
            if (saveFile.ShowDialog() == true)
                fileName = saveFile.FileName;
            else
                return;

            GetScxmlFile().Save(fileName, SaveOptions.None);

        }

        private XElement GetScxmlFile()
        {
            StateDictionary states = BuildStateDictionary();

            XDocument xdoc = new XDocument(
                new XDeclaration("1.0", "utf-8", "yes"),
                new XElement("Root", "Content")
            );

            XNamespace ns = "http://www.w3.org/2005/07/scxml";
            XElement stateMachineXml = new XElement(ns + "scxml");
            stateMachineXml.Add(new XAttribute("version", "1.0"));

            foreach (var state in states)
            {
                string stateType = string.Equals(states.FinalState, state.Key.Name, StringComparison.OrdinalIgnoreCase)
                    ? "Final" : "State";

                XElement stateXml = new XElement(stateType);
                stateXml.Add(new XAttribute("id", state.Key.Name));
                if (string.Equals(states.InitialState, state.Key.Name, StringComparison.OrdinalIgnoreCase))
                {
                    stateMachineXml.Add(new XAttribute("initial", state.Key.Name));
                }
                foreach (var transition in state.Value)
                {
                    XElement transitionXml = new XElement("transition");
                    transitionXml.Add(new XAttribute("event", transition.Key));
                    transitionXml.Add(new XAttribute("target", transition.Value.NextState));
                    stateXml.Add(transitionXml);
                }
                stateMachineXml.Add(stateXml);
            }

            // remove instances of xmlns="" from each node.
            foreach (XElement e in stateMachineXml.DescendantsAndSelf())
            {
                if (e.Name.Namespace == "")
                {
                    e.Name = ns + e.Name.LocalName;
                }
            }

            return stateMachineXml;

        }

        //private void SaveToDb_Executed(object sender, ExecutedRoutedEventArgs e)
        //{
        //    Database.SaveLoadToDatabase dbForm = new Database.SaveLoadToDatabase();

        //    StateMachineRecord stateMachine = new StateMachineRecord()
        //    {
        //        Name = StateMachineName,
        //        AssemblyType = AssemblyType,
        //        AssemblyLocation = AssemblyLocation,
        //        StateMachineDesigner = GetSmdFile().ToString(),
        //        StateMachineXml = GetScxmlFile().ToString()
        //    };

        //    stateMachine = dbForm.SaveStateMachine(stateMachine);
        //    SetDesignerValues(stateMachine);

        //}

        //private void LoadFromDb_Executed(object sender, ExecutedRoutedEventArgs e)
        //{
        //    Database.SaveLoadToDatabase dbForm = new Database.SaveLoadToDatabase();
        //    StateMachineRecord stateMachine = dbForm.LoadStateMachine();
        //    if (stateMachine != null)
        //    {
        //        OpenSmd(XElement.Parse(stateMachine.StateMachineDesigner));
        //        SetDesignerValues(stateMachine);
        //    }
        //}

        private void OpenSmf()
        {
            string smfFile = GetSmfFile();
            if (string.IsNullOrEmpty(smfFile)) return;
            Reset();
            SmfFileParser parser = new SmfFileParser(smfFile);
            DesignerCanvas.AllDomainProperties = DomainProperty.ConvertXElementsToDomainProperties(
                                                    parser.GetDomainProperties());

            this.DomainName = parser.GetDomainName();
            this.Namespace = parser.GetNamespace();
            this.AssemblyLocation = parser.GetAssemblyLocation();
            this.AssemblyType = parser.GetDomainType();
            DesignerCanvas.SmfFile = smfFile;

            SetDetails();

            XElement designerXml = parser.GetStateMachineDesigner();
            if (designerXml == null)
            {
                throw new ApplicationException("No designer Xml exists");
            }
            OpenDesignerXml(designerXml);
        }

        private void OpenComponent()
        {
            CustomCode.LoadComponent loadComponent = new CustomCode.LoadComponent();

            try
            {
                _componentModel = loadComponent.LoadComponentModel(_componentModel);
                if (_componentModel == null) return;
                Reset();

                DesignerCanvas.DbLocation = _componentModel.DbLocation;
                DesignerCanvas.SmfFile = _componentModel.SmfFile;
                this.DomainName = _componentModel.DomainName;
                this.AssemblyLocation = _componentModel.AssemblyLocation;
                this.AssemblyType = _componentModel.AssemblyType;

                SetDetails();

                DesignerCanvas.AllDomainProperties = _componentModel.DomainProperties;
                if (_componentModel.DesignerXml == null)
                {
                    throw new ApplicationException("No designer Xml exists");
                }
                OpenDesignerXml(_componentModel.DesignerXml);
                this.IsAssemblyLoaded = true;
            }
            finally
            {
                loadComponent.Close();
            }
        }

        private void SetDetails()
        {

            this.Details = string.IsNullOrEmpty(DesignerCanvas.SmfFile) ? "" : "Smf File: \t" + DesignerCanvas.SmfFile + "\n";
            this.Details += string.IsNullOrEmpty(this.AssemblyType) ? "" : "Assembly: \t" + this.AssemblyType + "\n";
            this.Details += string.IsNullOrEmpty(this.AssemblyLocation) ? "" : "Location: \t" + this.AssemblyLocation + "\n";
            this.Details += string.IsNullOrEmpty(DesignerCanvas.DbLocation) ? "" : "Database Folder: \t" + DesignerCanvas.DbLocation;
        }


        private void Reset()
        {
            this.Children.Clear();
            this.SelectionService.ClearSelection();
            this.DomainName = "[untitled]";
            this.Namespace = "";
            this.Details = "";
            this.AssemblyLocation = "";
            this.AssemblyType = "";
            DesignerCanvas.AllDomainProperties.Clear();
            DesignerCanvas.SmfFile = "";
            DesignerCanvas.DbLocation = "";
            IsAssemblyLoaded = false;
        }

        public void HighlightStates(List<string> highlightStates)
        {
            foreach (string state in highlightStates)
            {
                // todo - need to prevent user saving a state machine with duplicate states.
                foreach (var child in this.Children)
                {
                    DesignerItem stateItem = child as DesignerItem;
                    if (stateItem != null)
                    {
                        if (state.Equals(stateItem.DisplayText, StringComparison.OrdinalIgnoreCase))
                            stateItem.Highlight();
                        else
                            stateItem.RemoveHighlight();
                    }
                }
            }
        }

        public void HighlightTransitions(List<HighlightTransition> highlightTransitions)
        {
            foreach (HighlightTransition highlightTransition in highlightTransitions)
            {
                // todo - need to prevent user saving a state machine with duplicate states.
                foreach (var child in this.Children)
                {
                    Connection connection = child as Connection;
                    if (connection != null)
                    {
                        if (highlightTransition.EventName.Equals(connection.Event, StringComparison.OrdinalIgnoreCase))
                            if (connection.Source.ParentDesignerItem.DisplayText.Equals(highlightTransition.PreviousState, StringComparison.OrdinalIgnoreCase))
                                connection.Highlight();
                    }
                }
            }
        }

        public void RemoveAllHighlights()
        {
            foreach (var child in this.Children)
            {
                Connection connection = child as Connection;
                if (connection != null)
                {
                    connection.RemoveHighlight();
                }
                DesignerItem stateItem = child as DesignerItem;
                if (stateItem != null)
                {
                    stateItem.RemoveHighlight();
                }
            }
        }

    }

    public class DomainConverter : System.Windows.Data.IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return string.Format("{0}{1}{2}",
                values[0],
                values[0].ToString().Length == 0 ? "" : ".",
                values[1]);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            string[] splitValues = ((string)value).Split('.');
            return splitValues;
        }
    }
}
